import type { TmplAstElement } from '@angular-eslint/bundled-angular-compiler';
import { getTemplateParserServices } from '@angular-eslint/utils';
import type { TSESLint } from '@typescript-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
import { getDomElements } from '../utils/get-dom-elements';
import { toPattern } from '../utils/to-pattern';

export type Options = [];
export type MessageIds = 'mouseEventsHaveKeyEvents';
export const RULE_NAME = 'mouse-events-have-key-events';
const STYLE_GUIDE_LINK = 'https://www.w3.org/WAI/WCAG21/Understanding/keyboard';

const enum KeyEvents {
  Blur = 'blur',
  Focus = 'focus',
}

const enum MouseEvents {
  MouseOut = 'mouseout',
  MouseOver = 'mouseover',
}

export default createESLintRule<Options, MessageIds>({
  name: RULE_NAME,
  meta: {
    type: 'suggestion',
    docs: {
      description: `[Accessibility] Ensures that the mouse events \`${MouseEvents.MouseOut}\` and \`${MouseEvents.MouseOver}\` are accompanied by \`${KeyEvents.Focus}\` and \`${KeyEvents.Blur}\` events respectively. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users. See more at ${STYLE_GUIDE_LINK}`,
    },
    schema: [],
    messages: {
      mouseEventsHaveKeyEvents: `\`{{mouseEvent}}\` must be accompanied by \`{{keyEvent}}\` for accessibility (${STYLE_GUIDE_LINK})`,
    },
  },
  defaultOptions: [],
  create(context) {
    const parserServices = getTemplateParserServices(context);
    const domElementsPattern = toPattern([...getDomElements()]);
    const eventPairs = [
      [KeyEvents.Blur, MouseEvents.MouseOut],
      [KeyEvents.Focus, MouseEvents.MouseOver],
    ] as const;

    return eventPairs.reduce<Record<string, TSESLint.RuleFunction>>(
      (accumulator, [keyEvent, mouseEvent]) => ({
        ...accumulator,
        [`Element[name=${domElementsPattern}]:has(BoundEvent[name='${mouseEvent}']):not(:has(BoundEvent[name='${keyEvent}']))`]({
          sourceSpan,
        }: TmplAstElement) {
          const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);

          context.report({
            loc,
            messageId: 'mouseEventsHaveKeyEvents',
            data: { keyEvent, mouseEvent },
          });
        },
      }),
      {},
    );
  },
});

export const RULE_DOCS_EXTENSION = {
  rationale:
    'Elements that show or hide content on mouse hover (using mouseover and mouseout events) must also be accessible to keyboard users. When mouseover is used, there must be an equivalent focus event; when mouseout is used, there must be an equivalent blur event. This ensures that tooltips, dropdown menus, and other hover-triggered content can be accessed by users who navigate with keyboards instead of mice. For example, a tooltip that appears on mouseover should also appear when the element receives keyboard focus. This is essential for users with motor disabilities who cannot use a mouse, as well as screen reader users. This is a WCAG Level A requirement for keyboard accessibility.',
};
